Skip to content

fix: Address security vulnerabilities (deps + code scanning)#37

Merged
homeles merged 5 commits intomainfrom
fix/security-vulnerabilities
Mar 24, 2026
Merged

fix: Address security vulnerabilities (deps + code scanning)#37
homeles merged 5 commits intomainfrom
fix/security-vulnerabilities

Conversation

@lupita-hom
Copy link
Collaborator

Summary

Addresses Dependabot and CodeQL security alerts.

Dependency fixes (npm audit fix)

  • flatted (Prototype Pollution + recursion DoS)
  • socket.io-parser (unbounded binary attachments)
  • express-rate-limit (IPv4-mapped IPv6 bypass)
  • svgo (Billion Laughs DoS)
  • underscore (recursion DoS)
  • serialize-javascript (RCE)
  • jsonpath (arbitrary code injection)

Code fixes

  • NoSQL injection — sanitized user inputs in workflowController.js before MongoDB queries
  • ReDoS — fixed polynomial regex in workflowService.js
  • Format string — safe template usage in apiService.js
  • Log injection — sanitized logged values in server.js, workflowService.js, syncService.js

Files changed (7)

client/package-lock.json, client/src/api/apiService.js,
server/package-lock.json, server/server.js,
server/src/controllers/workflowController.js,
server/src/services/syncService.js, server/src/services/workflowService.js

lupita-hom and others added 3 commits March 24, 2026 09:20
…erabilities

Server: fixes express-rate-limit IPv4-mapped IPv6 bypass and socket.io-parser
unbounded binary attachments (2 high severity).

Client: fixes socket.io-parser unbounded binary attachments (1 high severity via
--legacy-peer-deps). Remaining 26 vulns are all locked inside react-scripts@5.0.1
transitive deps; npm audit fix --force would install react-scripts@0.0.0 (non-
functional), so those are deferred until react-scripts is replaced with Vite.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…s/services

NoSQL injection (workflowController.js): qs parses query strings like
?workflowName[$ne]=x into objects; passing those directly to MongoDB queries
allows operator injection. Now coercing workflowName to string (or null) before
use so only plain string values reach the query.

ReDoS (workflowService.js): replaced backtracking-prone /\((.*?)\)/ with the
non-backtracking /\(([^)]*)\)/ in two places (updateWorkflowJobs and
processWorkflowJobEvent). The character class [^)] cannot cause catastrophic
backtracking unlike the lazy .* quantifier.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ent log injection

Log injection allows an attacker to forge log entries by embedding CRLF sequences
in values that get passed to console.log/error. Fixed in four files:

- server/server.js: sanitize incoming webhook headers (x-github-event,
  x-github-delivery, content-type, x-hub-signature-256) before logging
- server/src/services/workflowService.js: sanitize run.status and
  workflow_job.status from GitHub payloads before logging
- server/src/services/syncService.js: sanitize repo.name, workflow.name, and
  run.id from GitHub API responses before embedding in error log messages
- client/src/api/apiService.js: sanitize repoName and id parameters before
  embedding in console.error messages (prevents misleading browser console output)

Each file adds a local sanitizeLog() helper that strips \r, \n, \t, and other
control characters (U+0000–U+001F, U+007F).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
Copy link

github-actions bot commented Mar 24, 2026

⚠️ Deprecation Warning: The deny-licenses option is deprecated for possible removal in the next major release. For more information, see issue 997.

Dependency Review

The following issues were found:
  • ✅ 0 vulnerable package(s)
  • ✅ 0 package(s) with incompatible licenses
  • ✅ 0 package(s) with invalid SPDX license definitions
  • ✅ 0 package(s) with unknown licenses.
  • ⚠️ 3 packages with OpenSSF Scorecard issues.
See the Details below.

Snapshot Warnings

⚠️: No snapshots were found for the head SHA 02af3ca.
Ensure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice.

OpenSSF Scorecard

Scorecard details
PackageVersionScoreDetails
npm/debug 4.4.3 ⚠️ 2.6
Details
CheckScoreReason
Code-Review🟢 3Found 11/30 approved changesets -- score normalized to 3
Binary-Artifacts🟢 10no binaries found in the repo
Dangerous-Workflow⚠️ -1no workflows found
Packaging⚠️ -1packaging workflow not detected
Maintained⚠️ 11 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 1
Token-Permissions⚠️ -1No tokens found
Pinned-Dependencies⚠️ -1no dependencies found
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Security-Policy⚠️ 0security policy file not detected
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Signed-Releases⚠️ -1no releases found
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
npm/flatted 3.4.2 🟢 4.3
Details
CheckScoreReason
Code-Review⚠️ 0Found 1/14 approved changesets -- score normalized to 0
Maintained🟢 1026 commit(s) and 2 issue activity found in the last 90 days -- score normalized to 10
Packaging⚠️ -1packaging workflow not detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Security-Policy🟢 4security policy file detected
Binary-Artifacts🟢 10no binaries found in the repo
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Pinned-Dependencies⚠️ 1dependency not pinned by hash detected -- score normalized to 1
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Signed-Releases⚠️ -1no releases found
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
SAST⚠️ 2SAST tool is not run on all commits -- score normalized to 2
npm/jsonpath 1.3.0 🟢 3.2
Details
CheckScoreReason
Code-Review⚠️ 2Found 5/22 approved changesets -- score normalized to 2
Packaging⚠️ -1packaging workflow not detected
Maintained🟢 89 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 8
Token-Permissions⚠️ -1No tokens found
Binary-Artifacts🟢 10no binaries found in the repo
Dangerous-Workflow⚠️ -1no workflows found
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Security-Policy⚠️ 0security policy file not detected
License🟢 10license file detected
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
Fuzzing⚠️ 0project is not fuzzed
Signed-Releases⚠️ -1no releases found
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
npm/sax 1.6.0 🟢 4.4
Details
CheckScoreReason
Packaging⚠️ -1packaging workflow not detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Code-Review⚠️ 1Found 5/30 approved changesets -- score normalized to 1
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Maintained🟢 89 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 8
Binary-Artifacts🟢 10no binaries found in the repo
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Signed-Releases⚠️ -1no releases found
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
Security-Policy🟢 10security policy file detected
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
npm/socket.io-parser 4.2.6 🟢 6.4
Details
CheckScoreReason
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Code-Review⚠️ 1Found 4/30 approved changesets -- score normalized to 1
Security-Policy🟢 10security policy file detected
Maintained🟢 1030 commit(s) and 17 issue activity found in the last 90 days -- score normalized to 10
Token-Permissions🟢 9detected GitHub workflow tokens with excessive permissions
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Binary-Artifacts🟢 9binaries present in source code
License🟢 10license file detected
Fuzzing⚠️ 0project is not fuzzed
Signed-Releases⚠️ -1no releases found
Branch-Protection⚠️ -1internal error: error during branchesHandler.setup: internal error: some github tokens can't read classic branch protection rules: https://github.com/ossf/scorecard-action/blob/main/docs/authentication/fine-grained-auth-token.md
Packaging🟢 10packaging workflow detected
Pinned-Dependencies⚠️ 1dependency not pinned by hash detected -- score normalized to 1
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
npm/svgo 2.8.2 🟢 6.2
Details
CheckScoreReason
Code-Review⚠️ 1Found 4/21 approved changesets -- score normalized to 1
Maintained🟢 109 commit(s) and 10 issue activity found in the last 90 days -- score normalized to 10
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions🟢 9detected GitHub workflow tokens with excessive permissions
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Binary-Artifacts🟢 10no binaries found in the repo
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
Packaging⚠️ -1packaging workflow not detected
License🟢 10license file detected
Fuzzing⚠️ 0project is not fuzzed
Signed-Releases⚠️ 0Project has not signed or included provenance with any releases.
Security-Policy🟢 10security policy file detected
Branch-Protection⚠️ -1internal error: error during branchesHandler.setup: internal error: some github tokens can't read classic branch protection rules: https://github.com/ossf/scorecard-action/blob/main/docs/authentication/fine-grained-auth-token.md
SAST🟢 10SAST tool is run on all commits
npm/terser-webpack-plugin 5.4.0 UnknownUnknown
npm/debug 4.4.3 ⚠️ 2.6
Details
CheckScoreReason
Code-Review🟢 3Found 11/30 approved changesets -- score normalized to 3
Binary-Artifacts🟢 10no binaries found in the repo
Dangerous-Workflow⚠️ -1no workflows found
Packaging⚠️ -1packaging workflow not detected
Maintained⚠️ 11 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 1
Token-Permissions⚠️ -1No tokens found
Pinned-Dependencies⚠️ -1no dependencies found
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Security-Policy⚠️ 0security policy file not detected
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Signed-Releases⚠️ -1no releases found
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
npm/express-rate-limit 8.3.1 UnknownUnknown
npm/ip-address 10.1.0 ⚠️ 2.5
Details
CheckScoreReason
Dangerous-Workflow⚠️ -1no workflows found
Packaging⚠️ -1packaging workflow not detected
Token-Permissions⚠️ -1No tokens found
Binary-Artifacts🟢 10no binaries found in the repo
Code-Review⚠️ 1Found 4/28 approved changesets -- score normalized to 1
Maintained⚠️ 01 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Pinned-Dependencies⚠️ -1no dependencies found
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Security-Policy⚠️ 0security policy file not detected
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Signed-Releases⚠️ -1no releases found
Branch-Protection⚠️ -1internal error: error during branchesHandler.setup: internal error: some github tokens can't read classic branch protection rules: https://github.com/ossf/scorecard-action/blob/main/docs/authentication/fine-grained-auth-token.md
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0
npm/socket.io-parser 4.2.6 🟢 6.4
Details
CheckScoreReason
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Code-Review⚠️ 1Found 4/30 approved changesets -- score normalized to 1
Security-Policy🟢 10security policy file detected
Maintained🟢 1030 commit(s) and 17 issue activity found in the last 90 days -- score normalized to 10
Token-Permissions🟢 9detected GitHub workflow tokens with excessive permissions
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Binary-Artifacts🟢 9binaries present in source code
License🟢 10license file detected
Fuzzing⚠️ 0project is not fuzzed
Signed-Releases⚠️ -1no releases found
Branch-Protection⚠️ -1internal error: error during branchesHandler.setup: internal error: some github tokens can't read classic branch protection rules: https://github.com/ossf/scorecard-action/blob/main/docs/authentication/fine-grained-auth-token.md
Packaging🟢 10packaging workflow detected
Pinned-Dependencies⚠️ 1dependency not pinned by hash detected -- score normalized to 1
SAST⚠️ 0SAST tool is not run on all commits -- score normalized to 0

Scanned Files

  • client/package-lock.json
  • server/package-lock.json

- NoSQL injection: validate repoPath with regex whitelist (owner/repo format)
- Format strings: use separate console.error arguments instead of template literals
- ReDoS: replace regex with indexOf/slice for parenthesis extraction
- Password hash: switch from SHA-256 to HMAC for token comparison
- Log injection: sanitize all remaining external values in log statements
- Remove all hashing from token comparison (pure timingSafeEqual with padding)
- Use %s format specifiers in console.log to avoid taint propagation
- Redact signature value from logs (only log presence)
console.log('Delivery ID:', id);
console.log('Content-Type:', contentType);
console.log('Signature:', signature);
console.log('Event: %s', sanitize(name));

Check warning

Code scanning / CodeQL

Log injection Medium

Log entry depends on a
user-provided value
.

Copilot Autofix

AI 1 day ago

In general, to fix log injection you should ensure any user-controlled value is normalized before being logged: strip or replace newline and carriage-return characters (and often other non-printable control characters), and clearly separate user data from static log text. For text logs, removing line breaks is typically sufficient; for HTML logs, HTML-encode.

In this file, the best fix with minimal behavioral change is to (1) make the sanitization function clearly focused on removing CR/LF (and, optionally, other control chars) and (2) apply it to all logged header-derived values. The code already does (2); we only need to adjust (1) to a simpler, recommendation-aligned implementation that static analysis tools are more likely to recognize. Specifically, in server/server.js around lines 107–113, replace the current sanitize definition that uses a broad control-character regex with a simpler one that explicitly strips \r and \n (and, if desired, a small, explicit additional set like \t). Leave the subsequent console.log calls as-is since they already use sanitize(...).

Concretely:

  • In server/server.js, update the sanitize helper inside the /api/webhooks/github handler to:
    • Coerce values to strings safely (handling null/undefined).
    • Replace \r and \n with spaces (or remove them).
  • No other changes to routing, logging, or imports are necessary.
Suggested changeset 1
server/server.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/server/server.js b/server/server.js
--- a/server/server.js
+++ b/server/server.js
@@ -105,7 +105,11 @@
   const contentType = req.headers['content-type'];
 
   // Sanitize header values before logging to prevent log injection via CRLF.
-  const sanitize = (v) => (v == null ? '' : String(v).replace(/[\r\n\t\x00-\x1F\x7F]/g, ' '));
+  // Remove carriage returns, newlines and tabs to ensure a single-line, well-formed log entry.
+  const sanitize = (v) =>
+    v == null
+      ? ''
+      : String(v).replace(/[\r\n\t]/g, ' ');
 
   console.log('Received webhook POST request from GitHub');
   console.log('Event: %s', sanitize(name));
EOF
@@ -105,7 +105,11 @@
const contentType = req.headers['content-type'];

// Sanitize header values before logging to prevent log injection via CRLF.
const sanitize = (v) => (v == null ? '' : String(v).replace(/[\r\n\t\x00-\x1F\x7F]/g, ' '));
// Remove carriage returns, newlines and tabs to ensure a single-line, well-formed log entry.
const sanitize = (v) =>
v == null
? ''
: String(v).replace(/[\r\n\t]/g, ' ');

console.log('Received webhook POST request from GitHub');
console.log('Event: %s', sanitize(name));
Copilot is powered by AI and may make mistakes. Always verify output.
console.log('Content-Type:', contentType);
console.log('Signature:', signature);
console.log('Event: %s', sanitize(name));
console.log('Delivery ID: %s', sanitize(id));

Check warning

Code scanning / CodeQL

Log injection Medium

Log entry depends on a
user-provided value
.

Copilot Autofix

AI 1 day ago

In general, the fix is to ensure that any user-controlled value written to logs is normalized so it cannot alter log structure (no CR/LF or other control chars) and is clearly delimited. That means replacing control characters with safe placeholders and optionally constraining to a conservative character set.

For this specific case, the best approach without changing functionality is to tighten the sanitize helper used at the logging point. We’ll (a) limit the header value to a reasonable length to avoid log flooding, and (b) more clearly strip all non-printable characters and normalize whitespace, then use that consistently in the log line. This keeps the logs human-readable, does not affect downstream logic (the raw id is still used later in webhooks.receive), and directly addresses CodeQL’s concern.

Concretely, in server/server.js inside the /api/webhooks/github handler:

  • Replace the current sanitize implementation with one that:
    • Converts null/undefined to an empty string.
    • Casts to string.
    • Removes all characters outside a safe printable range.
    • Collapses internal whitespace (including tabs) to single spaces.
    • Truncates to a fixed maximum length (e.g., 200 characters) to prevent log abuse.
  • Keep using sanitize(name), sanitize(id), and sanitize(contentType) in the console.log calls as before.

No new imports or external libraries are required; this can be done with plain string methods and regular expressions.

Suggested changeset 1
server/server.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/server/server.js b/server/server.js
--- a/server/server.js
+++ b/server/server.js
@@ -104,8 +104,20 @@
   const name = req.headers['x-github-event'];
   const contentType = req.headers['content-type'];
 
-  // Sanitize header values before logging to prevent log injection via CRLF.
-  const sanitize = (v) => (v == null ? '' : String(v).replace(/[\r\n\t\x00-\x1F\x7F]/g, ' '));
+  // Sanitize header values before logging to prevent log injection via CRLF and control characters.
+  const sanitize = (v) => {
+    if (v == null) return '';
+    // Convert to string and remove all non-printable ASCII characters.
+    let s = String(v).replace(/[\x00-\x1F\x7F]/g, ' ');
+    // Collapse consecutive whitespace to a single space.
+    s = s.replace(/\s+/g, ' ').trim();
+    // Limit length to avoid log flooding with extremely long header values.
+    const MAX_LEN = 200;
+    if (s.length > MAX_LEN) {
+      s = s.slice(0, MAX_LEN) + '...';
+    }
+    return s;
+  };
 
   console.log('Received webhook POST request from GitHub');
   console.log('Event: %s', sanitize(name));
EOF
@@ -104,8 +104,20 @@
const name = req.headers['x-github-event'];
const contentType = req.headers['content-type'];

// Sanitize header values before logging to prevent log injection via CRLF.
const sanitize = (v) => (v == null ? '' : String(v).replace(/[\r\n\t\x00-\x1F\x7F]/g, ' '));
// Sanitize header values before logging to prevent log injection via CRLF and control characters.
const sanitize = (v) => {
if (v == null) return '';
// Convert to string and remove all non-printable ASCII characters.
let s = String(v).replace(/[\x00-\x1F\x7F]/g, ' ');
// Collapse consecutive whitespace to a single space.
s = s.replace(/\s+/g, ' ').trim();
// Limit length to avoid log flooding with extremely long header values.
const MAX_LEN = 200;
if (s.length > MAX_LEN) {
s = s.slice(0, MAX_LEN) + '...';
}
return s;
};

console.log('Received webhook POST request from GitHub');
console.log('Event: %s', sanitize(name));
Copilot is powered by AI and may make mistakes. Always verify output.
console.log('Signature:', signature);
console.log('Event: %s', sanitize(name));
console.log('Delivery ID: %s', sanitize(id));
console.log('Content-Type: %s', sanitize(contentType));

Check warning

Code scanning / CodeQL

Log injection Medium

Log entry depends on a
user-provided value
.

Copilot Autofix

AI 1 day ago

General fix: ensure any user-controlled value written to logs is sanitized with a clearly defined, robust function that removes/neutralizes control characters and visually delimits user input. Then consistently use that function when logging such values.

Best concrete fix here:

  • Keep a single, named sanitizeForLog helper that:
    • Converts null/undefined to an empty string.
    • Converts the value to string.
    • Replaces all carriage returns, line feeds, and other control characters with a safe placeholder (space).
    • Optionally trims leading/trailing whitespace.
  • Use sanitizeForLog for each header before logging: name, id, and contentType.
  • Make user-controlled portions in log messages clearly delimited (e.g., wrapped in quotes).

Changes needed in server/server.js:

  • Replace the inline sanitize arrow function at line 108 with a named sanitizeForLog that is clearly oriented to log safety.
  • Update the console.log calls for Event, Delivery ID, and Content-Type to use sanitizeForLog and clearly mark the value (e.g., "Content-Type: '%s'").
  • No new imports are needed; all logic is implemented inline.

This keeps existing functionality (logging the same semantics) but hardens the sanitization and makes it clearer to both humans and tools that log injection has been intentionally addressed.

Suggested changeset 1
server/server.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/server/server.js b/server/server.js
--- a/server/server.js
+++ b/server/server.js
@@ -104,13 +104,20 @@
   const name = req.headers['x-github-event'];
   const contentType = req.headers['content-type'];
 
-  // Sanitize header values before logging to prevent log injection via CRLF.
-  const sanitize = (v) => (v == null ? '' : String(v).replace(/[\r\n\t\x00-\x1F\x7F]/g, ' '));
+  // Sanitize header values before logging to prevent log injection via CRLF
+  // and other control characters in plain-text logs.
+  const sanitizeForLog = (v) => {
+    if (v == null) {
+      return '';
+    }
+    // Convert to string and replace control characters (including CR/LF) with spaces.
+    return String(v).replace(/[\r\n\t\x00-\x1F\x7F]/g, ' ');
+  };
 
   console.log('Received webhook POST request from GitHub');
-  console.log('Event: %s', sanitize(name));
-  console.log('Delivery ID: %s', sanitize(id));
-  console.log('Content-Type: %s', sanitize(contentType));
+  console.log("Event: '%s'", sanitizeForLog(name));
+  console.log("Delivery ID: '%s'", sanitizeForLog(id));
+  console.log("Content-Type: '%s'", sanitizeForLog(contentType));
   console.log('Signature present:', Boolean(signature));
 
   if (!signature || !id || !name) {
EOF
@@ -104,13 +104,20 @@
const name = req.headers['x-github-event'];
const contentType = req.headers['content-type'];

// Sanitize header values before logging to prevent log injection via CRLF.
const sanitize = (v) => (v == null ? '' : String(v).replace(/[\r\n\t\x00-\x1F\x7F]/g, ' '));
// Sanitize header values before logging to prevent log injection via CRLF
// and other control characters in plain-text logs.
const sanitizeForLog = (v) => {
if (v == null) {
return '';
}
// Convert to string and replace control characters (including CR/LF) with spaces.
return String(v).replace(/[\r\n\t\x00-\x1F\x7F]/g, ' ');
};

console.log('Received webhook POST request from GitHub');
console.log('Event: %s', sanitize(name));
console.log('Delivery ID: %s', sanitize(id));
console.log('Content-Type: %s', sanitize(contentType));
console.log("Event: '%s'", sanitizeForLog(name));
console.log("Delivery ID: '%s'", sanitizeForLog(id));
console.log("Content-Type: '%s'", sanitizeForLog(contentType));
console.log('Signature present:', Boolean(signature));

if (!signature || !id || !name) {
Copilot is powered by AI and may make mistakes. Always verify output.
}

console.log('Starting sync for installation ID:', installationId);
console.log('Starting sync for installation ID: %s', sanitizeLog(installationId));

Check warning

Code scanning / CodeQL

Log injection Medium

Log entry depends on a
user-provided value
.

Copilot Autofix

AI 1 day ago

General approach: ensure that any user‑controlled data included in log messages is both validated and sanitized before logging. For IDs expected to be numeric, we can validate and coerce them to a safe representation; for arbitrary strings, we should strip control characters (including newlines) with a central sanitizeLog helper, and consistently apply it to any error messages or values that may originate from user input.

Best concrete fix here:

  1. Validate and normalize installationId once at the top of syncGitHubData:

    • Parse it with parseInt.
    • If parsing fails (NaN), throw an error before any logging.
    • Use this parsed integer (installationIdInt) everywhere (for logging and GitHub client calls).
    • When logging, we then log a non‑tainted integer, which removes the need to treat it as user input in that log entry.
  2. Sanitize error messages before logging in syncGitHubData’s catch block:

    • Wrap error.message with sanitizeLog when logging and when persisting into SyncHistory.results.
    • This ensures control characters in error messages (which sometimes embed raw input, such as IDs or user strings) cannot manipulate log structure.
  3. Keep the existing sanitizeLog helper as is and reuse it; we don’t introduce new dependencies or change public behavior of the API, only input validation/log formatting.

Concrete changes in server/src/services/syncService.js:

  • In syncGitHubData:
    • Introduce const installationIdInt = parseInt(installationId, 10); and validate it.
    • Update the log on line 55 to use installationIdInt (a safe integer, not user string).
    • Use installationIdInt instead of repeating parseInt(installationId, 10) for GitHub client initialization and installation lookup.
  • In the catch block (lines ~529–540):
    • Sanitize error.message when logging: console.error('Error in syncGitHubData:', sanitizeLog(error.message));
    • When building results.errors[0].error, use sanitizeLog(error.message) instead of the raw message.

No changes are required in server/src/routes/api.js for this issue.


Suggested changeset 1
server/src/services/syncService.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/server/src/services/syncService.js b/server/src/services/syncService.js
--- a/server/src/services/syncService.js
+++ b/server/src/services/syncService.js
@@ -52,20 +52,25 @@
         throw new Error('Installation ID is required for synchronization');
     }
 
-    console.log('Starting sync for installation ID: %s', sanitizeLog(installationId));
+    const installationIdInt = parseInt(installationId, 10);
+    if (Number.isNaN(installationIdInt)) {
+        throw new Error('Installation ID must be a valid integer');
+    }
+
+    console.log('Starting sync for installation ID: %s', installationIdInt);
     let syncRecord;
     
     try {
         // First ensure any existing in_progress syncs are marked as interrupted
         await markInterruptedSyncs();
 
-        const { app: octokit } = await getGitHubClient(parseInt(installationId, 10));
+        const { app: octokit } = await getGitHubClient(installationIdInt);
         console.log('GitHub client initialized');
 
         // Get the installation details first
         console.log('Fetching installation details...');
         const { data: installation } = await octokit.rest.apps.getInstallation({
-            installation_id: parseInt(installationId, 10)
+            installation_id: installationIdInt
         });
         
         const orgName = installation.account.login;
@@ -527,7 +521,7 @@
 
         return results;
     } catch (error) {
-        console.error('Error in syncGitHubData:', error);
+        console.error('Error in syncGitHubData:', sanitizeLog(error && error.message));
         if (syncRecord) {
             await SyncHistory.findByIdAndUpdate(syncRecord._id, {
                 status: 'failed',
@@ -535,7 +529,7 @@
                 results: {
                     errors: [{
                         type: 'sync',
-                        error: error.message
+                        error: sanitizeLog(error && error.message)
                     }]
                 }
             });
EOF
@@ -52,20 +52,25 @@
throw new Error('Installation ID is required for synchronization');
}

console.log('Starting sync for installation ID: %s', sanitizeLog(installationId));
const installationIdInt = parseInt(installationId, 10);
if (Number.isNaN(installationIdInt)) {
throw new Error('Installation ID must be a valid integer');
}

console.log('Starting sync for installation ID: %s', installationIdInt);
let syncRecord;

try {
// First ensure any existing in_progress syncs are marked as interrupted
await markInterruptedSyncs();

const { app: octokit } = await getGitHubClient(parseInt(installationId, 10));
const { app: octokit } = await getGitHubClient(installationIdInt);
console.log('GitHub client initialized');

// Get the installation details first
console.log('Fetching installation details...');
const { data: installation } = await octokit.rest.apps.getInstallation({
installation_id: parseInt(installationId, 10)
installation_id: installationIdInt
});

const orgName = installation.account.login;
@@ -527,7 +521,7 @@

return results;
} catch (error) {
console.error('Error in syncGitHubData:', error);
console.error('Error in syncGitHubData:', sanitizeLog(error && error.message));
if (syncRecord) {
await SyncHistory.findByIdAndUpdate(syncRecord._id, {
status: 'failed',
@@ -535,7 +529,7 @@
results: {
errors: [{
type: 'sync',
error: error.message
error: sanitizeLog(error && error.message)
}]
}
});
Copilot is powered by AI and may make mistakes. Always verify output.
@homeles homeles merged commit dc8c727 into main Mar 24, 2026
12 checks passed
@homeles homeles deleted the fix/security-vulnerabilities branch March 24, 2026 16:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants